<?php
declare(strict_types=1);
namespace Service\VenderClient\PayCenterApi;
use Service\Code\Normal;
use Service\BaseService;
use Service\ExtraService\TaskWorker;

class WeixinApiClient extends BaseService
{
    protected $config;
    protected $url = 'https://api.mch.weixin.qq.com/v3/pay/partner/transactions/jsapi';//统一下单接口
    protected $router = [
        'pay_scan'          => 'https://api.mch.weixin.qq.com/pay/micropay',//扫码
        'pay_scan_refund'   => 'https://api.mch.weixin.qq.com/secapi/pay/refund',//扫码退款
        'pay_mnapp'         => 'https://api.mch.weixin.qq.com/pay/unifiedorder',//统一下单接口
        'pay_query'         => 'https://api.mch.weixin.qq.com/pay/orderquery',//查询订单是否支付
        'pay_declare'       => 'https://api.mch.weixin.qq.com/cgi-bin/mch/customs/customdeclareorder',//订单申报接口
        'pay_declare_query' => 'https://api.mch.weixin.qq.com/cgi-bin/mch/customs/customdeclarequery',//订单申报查询接口
        'pay_declare_declare'=> 'https://api.mch.weixin.qq.com/cgi-bin/mch/customs/customdeclareredeclare',//订单申报重推接口
        'get_open_id'       => 'https://api.weixin.qq.com/sns/jscode2session',//获取openid
        'get_access_token'  => 'https://api.weixin.qq.com/cgi-bin/token',//获取access token
        'get_subscribe'     => 'https://api.weixin.qq.com/cgi-bin/user/info',//获取用户订阅
        'face_auth'         => 'https://payapp.weixin.qq.com/face/get_wxpayface_authinfo',//获取调用凭证
    ];
    
    public function __construct( array $apiBody = [] )
    {
        $this->config = $apiBody;
    }
    
    /**
     * 发起支付
     * @param array $body
     */
    public function scanPayOrder( array $body = [])
    {
        $total_fee      = bcmul((string)$body['total_fee'], '100');
        $timeStamp      = time();
        $timeStamp      = "$timeStamp";
        $getNonceStr    = getNonceStr();
        $signType       = "MD5";
        $request        = [
            'appid'             => $this->config['appid'],
            'mch_id'            => $this->config['mch_id'],
            'device_info'       => $body['device_info'],
            'attach'            => $body['attach'],
            'nonce_str'         => $getNonceStr,
            'sign_type'         => 'MD5',
            'body'              => $body['attach'],
            'out_trade_no'      => $body['out_trade_no'],
            'fee_type'          => 'CNY',
            'total_fee'         => (int)$total_fee,
            'spbill_create_ip'  => getIp()??'127.0.0.1',
            'auth_code'         => $body['auth_code']
        ];
        return $this->execute('pay_scan',$request);
    }
    
    /**
     * 扫码支付退款
     * @param type $param
     * @return type
     */
    public function scanPayRefund($param) {
        $total_fee      = bcmul((string)$param['total_fee'], '100');
        $refund_fee     = bcmul((string)$param['refund_fee'], '100');
        $getNonceStr    = getNonceStr();
        $request        = [
            'appid'             => $this->config['appid'],
            'mch_id'            => $this->config['mch_id'],
            'nonce_str'         => $getNonceStr,
            'sign_type'         => 'MD5',
            'out_trade_no'      => $param['out_trade_no'],
            'out_refund_no'     => $param['out_refund_no'],
            'total_fee'         => (int)$total_fee,
            'refund_fee'        => (int)$refund_fee,
            'refund_desc'       => $param['body']
        ];
        return $this->execute('pay_scan_refund',$request);
    }

    /**
     * 小程序支付
     * @param array $body
     */
    public function mnAppPay( array $body = [])
    {
        $timeStamp      = time();
        $timeStamp      = "$timeStamp";
        $getNonceStr    = getNonceStr();
        $signType       = "MD5";
        $order_sn       = $body['out_trade_no'];
        $request        = [
            'appid'             => $this->config['appid'],
            'mch_id'            => $this->config['mch_id'],
            'attach'            => $body['attach'],
            'nonce_str'         => $getNonceStr,
            'sign_type'         => $signType,
            'body'              => $body['attach'],
            'out_trade_no'      => $order_sn,
            'fee_type'          => 'CNY',
            'total_fee'         => (int)$body['total_fee'],
            'spbill_create_ip'  => '127.0.0.1',
            'trade_type'        => $body['trade_type'],
            'notify_url'        => $body['notify_url'],
            'openid'            => $body['openid'] ??''
        ];
        
        $result = $this->execute('pay_mnapp',$request);
        if( $result['code'] != Normal::SUC ) {
            return $result;
        }
        
        $pay_result = $result['data'];
        $preload = [
            'appId'         => $this->config['appid'],
            'timeStamp'     => $timeStamp,
            'nonceStr'      => $getNonceStr,
            'package'       => "prepay_id={$pay_result['prepay_id']}",
            'signType'      => $signType,
        ];
        $preload['paySign'] = $this->MakeSign($preload);
        $preload['order_sn'] = $order_sn;
        return Normal::returnCode(Normal::SUC,"获取预处理支付成功",$preload);
    }
    
    /**
     * 小程序刷脸支付获取调用凭证
     * @param array $body
     */
    public function mnAppgetUserAuth( array $body = [])
    {
        $timeStamp      = time();
        $timeStamp      = "$timeStamp";
        $getNonceStr    = getNonceStr();
        $signType       = "MD5";
        $request        = [
            'store_id'     => $body['store_id'],
            'store_name'   => $body['store_name'],
            'device_id'    => $body['device_id'],
            'rawdata'      => $body['rawdata'],
            'appid'        => $this->config['appid'],
            'mch_id'       => $this->config['mch_id'],
            'now'          => (int)$timeStamp,
            'version'      => '1',
            'sign_type'    => $signType,
            'nonce_str'    => $getNonceStr,
        ];
    
        $result = $this->execute('face_auth',$request);
        print_r($result);die;
        if( $result['code'] != Normal::SUC ) {
            return $result;
        }
    
        $pay_result = $result['data'];
        $preload = [
            'appId'         => $this->config['appid'],
            'timeStamp'     => $timeStamp,
            'nonceStr'      => $getNonceStr,
            'package'       => "prepay_id={$pay_result['prepay_id']}",
            'signType'      => $signType,
            ];
        $preload['paySign'] = $this->MakeSign($preload);
        $preload['order_sn'] = $order_sn;
        return Normal::returnCode(Normal::SUC,"获取预处理支付成功",$preload);
    }
    
    /**
     * 查询微信用户openid
     * @param string $js_code
     */
    public function getOpenID(string $js_code)
    {
        $request = [
            'js_code'   => $js_code,
        ];
        return $this->execute('get_open_id',$request);
    }
    
    /**
     * 实现用户订阅
     * @param string $js_code
     */
    public function getSubscribe(string $access_token,string $open_id)
    {
        $request = [
            'access_token'  => $access_token,
            'open_id'       => $open_id,
        ];
        return $this->execute('get_subscribe',$request);
    }
    
    /**
     * 获取accessToken
     * @param string $js_code
     */
    public function getAccessToken()
    {
        $request = [];
        return $this->execute('get_access_token',$request);
    }
    
    /**
     * 微信支付单申报
     * @param array $body
     */
    public function payDeclare( array $body)
    {
        $request = [
            'appid'             => $this->config['appid'],
            'mch_id'            => $this->config['mch_id'],
            'out_trade_no'      => (string)$body['out_trade_no'],
            'transaction_id'    => $body['transaction_id']??'',
            'customs'           => $body['customs'],
            'mch_customs_no'    => $body['eCompanyCode']
        ];
        if(isset($body['sub_order_no'])){
            $request["sub_order_no"] = $body['sub_order_no'];
            $request["fee_type"] = 'CNY';
            $request["order_fee"] = bcmul((string)$body['order_fee'], '100')??0;
            if($body['transport_fee']>0){
                $request["transport_fee"] = bcmul((string)$body['transport_fee'], '100')??0;
            }
            $request["product_fee"] = bcmul((string)$body['product_fee'], '100')??0;
        }
        return $this->execute('pay_declare',$request);
    }
    
    /**
     * 支付单申报查询
     * @param array $param
     * @return type
     */
    public function payDeclareQuery(array $param) {
        $data=[
            'appid'     => $this->config['appid'],
            'mch_id'    => $this->config['mch_id'],
            'customs'   => $param['customs']??'GUANGZHOU_ZS',
            'sign_type' => 'MD5',
        ];
        if(isset($param['sub_order_no'])){
            $data["sub_order_no"] = $param['sub_order_no'];
        }else if(isset($param['sub_order_id'])){
            $data["sub_order_id"] = $param['sub_order_id'];
        }else{
            return Normal::returnCode(Normal::FAIL, '主订单号或者子订单号必传');
        }
        return $this->execute('pay_declare_query',$data);
    }
    
    /**
     * 微信支付单重推
     * @param array $param
     * @return type
     */
    public function payDeclareReCommit(array $param) {
        $data=[
            'appid'     => $this->config['appid'],
            'mch_id'    => $this->config['mch_id'],
            'out_trade_no'      => (string)$param['out_trade_no'],
            'customs'   => $param['customs']??'GUANGZHOU_ZS',
            'mch_customs_no'    => $param['eCompanyCode'],
            'sign_type' => 'MD5',
        ];
        if(isset($param['sub_order_no'])){
            $data["sub_order_no"] = $param['sub_order_no'];
        }else if(isset($param['sub_order_id'])){
            $data["sub_order_id"] = $param['sub_order_id'];
        }else{
            return Normal::returnCode(Normal::FAIL, '主订单号或者子订单号必传');
        }
        return $this->execute('pay_declare_declare',$data);
    }

    /**
     * 查询支付结果情况
     * @param array $body
     */
    public function queryPay( array $body)
    {
        $timeStamp      = time();
        $timeStamp      = "$timeStamp";
        $getNonceStr    = getNonceStr();
        $request        = [
            'appid'             => $this->config['appid'],
            'mch_id'            => $this->config['mch_id'],
            'out_trade_no'      => $body['out_trade_no'],
            'nonce_str'         => $getNonceStr,
            'sign_type'         => 'MD5'
        ];
        return $this->execute('pay_query',$request);
    }

    /**
     * 生成签名
     * @param WxPayConfigInterface $config  配置对象
     * @param bool $needSignType  是否需要补signtype
     * @return 签名，本函数不覆盖sign成员变量，如要设置签名需要调用SetSign方法赋值
     */
    public function makeSign($payParameter)
    {
        ksort($payParameter);
        $string = $this->toUrlParams($payParameter);
        $string = $string."&key=".$this->config['key'];    
        $string = md5($string);
        //签名步骤四：所有字符转为大写
        $result = strtoupper($string);
        return $result;
    }
    
    /**
     * 格式化参数格式化成url参数
     */
    public function toUrlParams( $param = [] )
    {
        $buff = "";
        foreach ($param as $k => $v)
        {
            if($k != "sign" && $v != "" && !is_array($v)){
                $buff .= $k . "=" . $v . "&";
            }
        }
    
        $buff = trim($buff, "&");
        return $buff;
    }
    
    /**
     * 输出xml字符
     * @throws WxPayException
     **/
    public function toXml($payParameter)
    {
        $xml = "<xml>";
        foreach ($payParameter as $key=>$val)
        {
            if (is_numeric($val)){
                $xml.="<".$key.">".$val."</".$key.">";
            }else{
                $xml.="<".$key."><![CDATA[".$val."]]></".$key.">";
            }
        }
        $xml.="</xml>";
        return $xml;
    }
    
    public function xmlToArray($xml){
        libxml_disable_entity_loader(true);
        return json_decode(json_encode(simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA)), true);
    }
    
    /**
     * 推送接口
     * @param string $method
     * @param array $body
     */
    public function execute(string $method, array $data)
    {
        if( !isset($this->router[$method]) ) {
            return Normal::returnCode(Normal::FAIL,"当前方式路由不存在【{$method}】");
        }
        
        $url = $this->router[$method];
        $data['sign'] = $this->makeSign($data);
        
        //获取特殊信息
        if( in_array($method,['get_access_token','get_open_id','get_subscribe']))
        {
            if( $method == 'get_open_id' ) {
                $url = "{$url}?appid={$this->config['appid']}&secret={$this->config['appsecret']}&js_code={$data['js_code']}&grant_type=authorization_code";
            } elseif ( $method == 'get_access_token' ) {
                $url = "{$url}?grant_type=client_credential&appid={$this->config['appid']}&secret={$this->config['appsecret']}";
            } elseif ( $method == 'get_subscribe' ) {
                $url="{$url}?access_token={$data['access_token']}&openid={$data['open_id']}&lang=zh_CN";
            } else {
                return Normal::returnCode(Normal::FAIL,"当前方法{$method}不支持");
            }
            $parameter = [
                [
                    'tag'       => 'weixin_'.$method,
                    'url'       => $url,
                    'data'      => [],
                    'method'    => 'get',
                    'format'    => 'form-data',
                    'header'    => [
                    ],
                ],
            ];
            $resTask    = TaskWorker::instance()->addTask($parameter);
            $resTask    = current($resTask);
            $response   = @json_decode($resTask['response']['result'],true);
            if( $method == 'get_access_token' ) 
            {
                if( isset($response['access_token']) )
                {
                    return Normal::returnCode(Normal::SUC,"获取成功",$response); 
                }
                
                return Normal::returnCode(Normal::FAIL,"请求处理失败[{$reson}]");
            } 
            else 
            {
                if( isset($response['errcode']) && $response['errcode'] == 0 ) {
                    return Normal::returnCode(Normal::SUC,"获取成功",$response);
                }
            }
            
            $reason = json_encode($resTask['response'],JSON_UNESCAPED_UNICODE);
            return Normal::returnCode(Normal::FAIL,"处理失败【{$reason}】");
        } 
        else 
        {
            $cert=[];
            if($method=='pay_scan_refund'){
                $sslcert=__DIR__."/WechatCert/".$this->config['cert']."/apiclient_cert.pem";
                $sslkey=__DIR__."/WechatCert/".$this->config['cert']."/apiclient_key.pem";
                if(!file_exists($sslcert)||!file_exists($sslkey)){
                    return Normal::returnCode(Normal::FAIL,"处理失败【微信退款缺少ssl证书】");
                }
                $cert=[
                    'sslcert'=>$sslcert,
                    'sslkey'=>$sslkey,
                ];
            }
            $parameter = [
                [
                    'tag'       => 'weixin_'.$method,
                    'url'       => $url,
                    'data'      => $this->toXml($data),
                    'method'    => 'post',
                    'format'    => 'xml',
                    'header'    => [
                    ],
                    'cert'      => $cert
                ],
            ];
            
            $resTask    = TaskWorker::instance()->addTask($parameter);
            $resTask    = current($resTask);
            $response   = $this->xmlToArray($resTask['response']['result']);
            if( isset($response['return_code']) && $response['return_code'] == 'SUCCESS' ) {
                if( $response['result_code'] == 'SUCCESS' ) {
                    return Normal::returnCode(Normal::SUC,"处理成功",$response);
                }
            
                $reason = $response['err_code_des'] ?? json_encode($response,JSON_UNESCAPED_UNICODE);
                return Normal::returnCode(Normal::FAIL,$reason,$response);
            }
            $reason = $response['return_msg'] ?? $response['return_msg'];
            return Normal::returnCode(Normal::FAIL,"处理失败【{$reason}】",$response);
        }
    }
}
